//ssock_tcpsock.c
/*
 * Copyright 2010 Samuel C. Payson. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *	
 * THIS SOFTWARE IS PROVIDED BY SAMUEL C. PAYSON ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SAMUEL C. PAYSON OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *	
 * The views and conclusions contained in the software and documentation are those of the
 * authors and should not be interpreted as representing official policies, either expressed
 * or implied, of Samuel C. Payson.
*/
#include <ssock/ssock_tcpsock.h>

#define NEWTCP_RETURNS_NULL

inline int ssock_tcpconnect(ssock_tcpsocket * sock)
{
	return connect(sock->tcp_fd, sock->tcp_ai.ai_addr, sock->tcp_ai.ai_addrlen);
}

inline void ssock_tcpclose(ssock_tcpsocket * sock)
{
	close(sock->tcp_fd);
}

inline void ssock_freetcp(ssock_tcpsocket * sock)
{
	free(sock);
}

ssock_tcpsocket * ssock_newtcp(const char * node, const char * port)
{
	int status;
	struct addrinfo hints, * addrtop;
	ssock_tcpsocket * r = malloc(sizeof(ssock_tcpsocket));
	memset(&hints, 0, sizeof(struct addrinfo));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	if (!node)
		hints.ai_flags = AI_PASSIVE;
	status = getaddrinfo(node, port, &hints, &addrtop);
	if (status)
	{
#ifndef NEWTCP_RETURNS_NULL
		r->tcp_fd = -1;
		memset(&r->tcp_ai, 0, sizeof(r->tcp_ai));
		r->tcp_port = 0x0;
		return r;
#else  /*  *  *  *  *  *  */
		return NULL;
#endif /*  *  *  *  *  *  */
	}
	else
	{
		struct addrinfo * seek = addrtop;
		r->tcp_fd = -1;
		while (r->tcp_fd == -1 && seek)
		{
			r->tcp_fd = socket(seek->ai_family, seek->ai_socktype, seek->ai_protocol);
			if (r->tcp_fd == -1)
			{
			bad_ai: //I know, I know... who uses a goto statement?
				r->tcp_fd = -1;
				seek = seek->ai_next;
				continue;
			}
			else if (!node)
			{
				int yes = 1;
				r->tcp_ai = *seek;
				if (setsockopt(r->tcp_fd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1)
					goto bad_ai;
				else if (bind(r->tcp_fd, r->tcp_ai.ai_addr, r->tcp_ai.ai_addrlen) == -1)
					goto bad_ai;
				else if (listen(r->tcp_fd, SSOCK_LISTEN_BACKLOG) == -1)
					goto bad_ai;
			}
		}
		freeaddrinfo(addrtop);
	}
	return r;
}

int ssock_tcpsend(ssock_tcpsocket * sock, void * data, size_t sz)
{
	size_t sent = 0;
	size_t this;
	uint8_t * seek;
	while (sent < sz)
	{
		seek = (uint8_t *)data + sent;
		this = send(sock->tcp_fd, seek, sizeof(uint8_t) * (sz - sent), 0x0);
		sent += this;
		if (this < 0)
			return this;
	}
	return sent;
}
/*	ssock_packet WAS A TERRIBLE IDEA, JUST USES EXTRA MEMORY
	AND I'M SURE PEOPLE ARE MORE COMFORTABLE MANAGING BYTES
ssock_packet * ssock_tcprecv_alloc(ssock_tcpsocket * sock)
{
	ssock_packet * r = malloc(sizeof(ssock_packet));
	r->dynalloc = 1;
	r->sz = recv(sock->tcp_fd, _ssock_recvbuffer, SSOCK_RECV_BUFFER_SIZE, 0);
	r->data = malloc(r->sz);
	memcpy(r->data, _ssock_recvbuffer, r->sz);
	return r;
}

void ssock_enable_volatile_recv(size_t buff_size)
{
	
}

ssock_packet * ssock_tcprecv_volatile(ssock_tcpsocket * sock)
{
	ssock_packet * r = malloc(sizeof(ssock_packet));
	r->dynalloc = 0;
	r->sz = recv(sock->tcp_fd, _ssock_recvbuffer, SSOCK_RECV_BUFFER_SIZE, 0);
	r->data = _ssock_recvbuffer;
	return r;
}*/

//For receiving information which you already know the size of.
int ssock_tcprecv(ssock_tcpsocket * sock, void * buffer, size_t sz)
{
	int recvd;
	size_t total = 0;
	while (total < sz)
	{
		recvd = recv(sock->tcp_fd, (uint8_t *)buffer + total, sz - total, 0);
		if (recvd == -1)
			return -1;
		else
			total += recvd;
	}
	return total;
}

//Takes whatever information is waiting on your socket (up to 'sz' bytes)
//and puts it into the memory pointed to by 'buffer'. As the code shows,
//it works just like the 'recv(//)' system call
inline int ssock_tcprecv_next(ssock_tcpsocket * sock, void * buffer, size_t sz)
{
	return recv(sock->tcp_fd, (uint8_t *)buffer, sz, 0);
}

/*int ssock_tcplisten(ssock_tcpsocket * sock)
{
	return 0;
}*/

ssock_tcpsocket * ssock_tcpaccept(ssock_tcpsocket * listener)
{
	socklen_t sz;
	struct sockaddr_storage nAddr;
	sz = sizeof(struct sockaddr_storage);
	ssock_tcpsocket * r = malloc(sizeof(ssock_tcpsocket));
	SOCKET accepted = accept(listener->tcp_fd, (struct sockaddr *)&nAddr, &sz);
	if (accepted == -1)
		return NULL;
	r->tcp_fd = accepted;
	return r;
}